﻿// This is an open source non-commercial project. Dear PVS-Studio, please check it.
// PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com

// ReSharper disable CheckNamespace
// ReSharper disable CommentTypo
// ReSharper disable IdentifierTypo
// ReSharper disable UnusedMember.Global
// ReSharper disable UnusedType.Global

/* PftConditionalStatement.cs -- условный оператор
 * Ars Magna project, http://arsmagna.ru
 */

#region Using directives

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.IO;

using AM;
using AM.Text;

using ManagedIrbis.Pft.Infrastructure.Compiler;
using ManagedIrbis.Pft.Infrastructure.Diagnostics;
using ManagedIrbis.Pft.Infrastructure.Serialization;
using ManagedIrbis.Pft.Infrastructure.Text;

using Microsoft.Extensions.Logging;

#endregion

#nullable enable

namespace ManagedIrbis.Pft.Infrastructure.Ast;

/// <summary>
/// Условный оператор <code>if условие then ... else fi</code>.
/// </summary>
public sealed class PftConditionalStatement
    : PftNode
{
    #region Properties

    /// <summary>
    /// Собственно условие.
    /// </summary>
    public PftCondition? Condition { get; set; }

    /// <summary>
    /// Ветвь ИНАЧЕ.
    /// </summary>
    public PftNodeCollection ElseBranch { get; private set; }

    /// <summary>
    /// Ветвь ТОГДА.
    /// </summary>
    public PftNodeCollection ThenBranch { get; private set; }

    /// <inheritdoc cref="PftNode.Children" />
    public override IList<PftNode> Children
    {
        get
        {
            if (_virtualChildren is null)
            {
                _virtualChildren = new VirtualChildren();
                var nodes = new List<PftNode>();
                if (!ReferenceEquals (Condition, null))
                {
                    nodes.Add (Condition);
                }

                nodes.AddRange (ThenBranch);
                nodes.AddRange (ElseBranch);
                _virtualChildren.SetChildren (nodes);
            }

            return _virtualChildren;
        }

        [ExcludeFromCodeCoverage]
        protected set
        {
            // Nothing to do here

            Magna.Logger.LogError
                (
                    nameof (PftConditionalStatement) + "::" + nameof (Children)
                    + ": set value={Value}",
                    value.ToVisibleString()
                );
        }
    }

    /// <inheritdoc cref="PftNode.ComplexExpression" />
    public override bool ComplexExpression => true;

    #endregion

    #region Construction

    /// <summary>
    /// Конструктор по умолчанию.
    /// </summary>
    public PftConditionalStatement()
    {
        ElseBranch = new PftNodeCollection (this);
        ThenBranch = new PftNodeCollection (this);
    }

    /// <summary>
    /// Конструктор.
    /// </summary>
    public PftConditionalStatement
        (
            PftToken token
        )
        : base (token)
    {
        token.MustBe (PftTokenKind.If);

        ElseBranch = new PftNodeCollection (this);
        ThenBranch = new PftNodeCollection (this);
    }

    /// <summary>
    /// Конструктор.
    /// </summary>
    public PftConditionalStatement
        (
            PftCondition condition,
            params PftNode[] thenBranch
        )
        : this()
    {
        Condition = condition;
        foreach (var node in thenBranch)
        {
            ThenBranch.Add (node);
        }
    }

    #endregion

    #region Private members

    private VirtualChildren? _virtualChildren;

    #endregion

    #region ICloneable members

    /// <inheritdoc cref="ICloneable.Clone" />
    public override object Clone()
    {
        var result = (PftConditionalStatement) base.Clone();

        result._virtualChildren = null;
        result.Condition = (PftCondition?) Condition?.Clone();
        result.ElseBranch = ElseBranch.CloneNodes (result).ThrowIfNull();
        result.ThenBranch = ThenBranch.CloneNodes (result).ThrowIfNull();

        return result;
    }

    #endregion

    #region PftNode members

    /// <inheritdoc cref="PftNode.CompareNode" />
    internal override void CompareNode
        (
            PftNode otherNode
        )
    {
        Sure.NotNull (otherNode);

        base.CompareNode (otherNode);

        var otherStatement = (PftConditionalStatement) otherNode;
        PftSerializationUtility.CompareNodes
            (
                Condition,
                otherStatement.Condition
            );
        PftSerializationUtility.CompareLists
            (
                ThenBranch,
                otherStatement.ThenBranch
            );
        PftSerializationUtility.CompareLists
            (
                ElseBranch,
                otherStatement.ElseBranch
            );
    }

    /// <inheritdoc cref="PftNode.Compile" />
    public override void Compile
        (
            PftCompiler compiler
        )
    {
        Sure.NotNull (compiler);

        if (Condition is null)
        {
            throw new PftCompilerException();
        }

        Condition.Compile (compiler);
        compiler.CompileNodes (ThenBranch);
        compiler.CompileNodes (ElseBranch);

        compiler.StartMethod (this);

        compiler
            .WriteIndent()
            .Write ("bool flag = ")
            .CallNodeMethod (Condition)
            .WriteIndent()
            .WriteLine ("if (flag)")
            .WriteIndent()
            .WriteLine ("{")
            .IncreaseIndent()
            .CallNodes (ThenBranch)
            .DecreaseIndent()
            .WriteIndent()
            .WriteLine ("}");
        if (ElseBranch.Count != 0)
        {
            compiler
                .WriteIndent()
                .WriteLine ("else")
                .WriteIndent()
                .WriteLine ("{")
                .IncreaseIndent()
                .CallNodes (ElseBranch)
                .DecreaseIndent()
                .WriteIndent()
                .WriteLine ("}");
        }

        compiler.EndMethod (this);
        compiler.MarkReady (this);
    }

    /// <inheritdoc cref="PftNode.Deserialize" />
    protected internal override void Deserialize
        (
            BinaryReader reader
        )
    {
        Sure.NotNull (reader);

        base.Deserialize (reader);
        Condition = (PftCondition?) PftSerializer.DeserializeNullable (reader);
        PftSerializer.Deserialize (reader, ThenBranch);
        PftSerializer.Deserialize (reader, ElseBranch);
    }

    /// <inheritdoc cref="PftNode.Execute" />
    public override void Execute
        (
            PftContext context
        )
    {
        Sure.NotNull (context);

        OnBeforeExecution (context);

        if (Condition is null)
        {
            Magna.Logger.LogError
                (
                    nameof (PftConditionalStatement) + "::" + nameof (Execute)
                    + ": condition not set"
                );

            throw new PftSyntaxException();
        }

        if (ThenBranch.Count == 0
            && ElseBranch.Count == 0)
        {
            Magna.Logger.LogWarning
                (
                    nameof (PftConditionalStatement) + "::" + nameof (Execute)
                    + ": empty Then and Else branches"
                );
        }

        Condition.Execute (context);

        if (Condition.Value)
        {
            foreach (var child in ThenBranch)
            {
                child.Execute (context);
            }
        }
        else
        {
            foreach (var child in ElseBranch)
            {
                child.Execute (context);
            }
        }

        OnAfterExecution (context);
    }

    /// <inheritdoc cref="PftNode.GetNodeInfo" />
    public override PftNodeInfo GetNodeInfo()
    {
        var result = new PftNodeInfo
        {
            Node = this,
            Name = SimplifyTypeName (GetType().Name)
        };

        if (Condition is not null)
        {
            var conditionNode = new PftNodeInfo
            {
                Node = Condition,
                Name = "Condition"
            };
            result.Children.Add (conditionNode);
            conditionNode.Children.Add (Condition.GetNodeInfo());
        }

        var thenNode = new PftNodeInfo
        {
            Name = "Then"
        };
        foreach (var node in ThenBranch)
        {
            thenNode.Children.Add (node.GetNodeInfo());
        }

        result.Children.Add (thenNode);

        if (ElseBranch.Count != 0)
        {
            var elseNode = new PftNodeInfo
            {
                Name = "Else"
            };
            foreach (var node in ElseBranch)
            {
                elseNode.Children.Add (node.GetNodeInfo());
            }

            result.Children.Add (elseNode);
        }

        return result;
    }

    /// <inheritdoc cref="PftNode.Optimize"/>
    public override PftNode? Optimize()
    {
        Condition = (PftCondition?) Condition?.Optimize();

        ThenBranch.Optimize();
        ElseBranch.Optimize();

        if (ThenBranch.Count == 0
            && ElseBranch.Count == 0)
        {
            return null;
        }

        return this;
    }

    /// <inheritdoc cref="PftNode.PrettyPrint" />
    public override void PrettyPrint
        (
            PftPrettyPrinter printer
        )
    {
        //bool needComment = false;

        printer.EatWhitespace();
        printer.EatNewLine();

        printer
            .WriteLine()
            .WriteIndent()
            .Write ("if ");

        Condition?.PrettyPrint (printer);

        printer
            .WriteLine()
            .WriteIndent()
            .Write ("then ");

        var isComplex = PftUtility.IsComplexExpression (ThenBranch);
        if (isComplex)
        {
            //needComment = true;
            printer.IncreaseLevel();
            printer.EatWhitespace();
            printer.EatNewLine();
            printer.WriteLine();
            printer.WriteIndent();
        }

        printer.WriteNodes (ThenBranch);
        if (isComplex)
        {
            printer.DecreaseLevel();
            printer.WriteLine();
        }

        if (ElseBranch.Count != 0)
        {
            isComplex = PftUtility.IsComplexExpression (ElseBranch);
            printer.EatNewLine();
            if (ThenBranch.Count != 0)
            {
                printer
                    .WriteLine()
                    .WriteIndent();
            }

            printer.Write ("else ");
            if (isComplex)
            {
                //needComment = true;
                printer.IncreaseLevel();
                printer.EatWhitespace();
                printer.EatNewLine();
                printer.WriteLine();
                printer.WriteIndent();
            }

            printer.WriteNodes (ElseBranch);
            if (isComplex)
            {
                printer.DecreaseLevel();
                printer.WriteLine();
            }
        }

        printer.EatWhitespace();
        printer.EatNewLine();
        printer.WriteLine();

        printer
            .WriteIndentIfNeeded()
            .Write ("fi,");

        //if (needComment
        //    && !ReferenceEquals(Condition, null))
        //{
        //    printer.Write(" /* ");
        //    Condition.PrettyPrint(printer);
        //}

        printer.WriteLine();
    }

    /// <inheritdoc cref="PftNode.Serialize" />
    protected internal override void Serialize
        (
            BinaryWriter writer
        )
    {
        base.Serialize (writer);
        PftSerializer.SerializeNullable (writer, Condition);
        PftSerializer.Serialize (writer, ThenBranch);
        PftSerializer.Serialize (writer, ElseBranch);
    }

    /// <inheritdoc cref="PftNode.ShouldSerializeText" />
    [DebuggerStepThrough]
    protected internal override bool ShouldSerializeText()
    {
        return false;
    }

    #endregion

    #region Object members

    /// <inheritdoc cref="Object.ToString" />
    public override string ToString()
    {
        var builder = StringBuilderPool.Shared.Get();
        builder.Append ("if ");
        builder.Append (Condition);

        builder.Append (" then");
        foreach (var node in ThenBranch)
        {
            builder.Append (' ');
            builder.Append (node);
        }

        if (ElseBranch.Count != 0)
        {
            builder.Append (" else");
            foreach (var node in ElseBranch)
            {
                builder.Append (' ');
                builder.Append (node);
            }
        }

        builder.Append (" fi");

        return builder.ReturnShared();
    }

    #endregion
}
